/*
 * Copyright 2009 Joseph Wu
 * Distributed under the Boost Software License, Version 1.0.
 * (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
 *
 * For more information, see http://xpcomplus.sourceforge.net
 */

#include <algorithm>
#include <cstring>
#include <boost/test/unit_test.hpp>
#include <xpcom/ptr_array.hpp>
#include <xpcom/memory.hpp>
#include <xpcom/impl/ptr_array.hpp>
#include "interface_def.hpp"

namespace xpcom {
namespace {

void do_in_impl_1(const ptr_array<IBase>& a)
{
    BOOST_CHECK(a.empty());
}

void do_in_1(IBase** a, uint32_t s, uint32_t l)
{
    do_in_impl_1(impl::ptr_array_in<IBase>(a, s, l));
}

void do_in2_1(IBase** a, uint32_t s)
{
    do_in_impl_1(impl::ptr_array_in2<IBase>(a, s));
}

void do_in_impl_2(const ptr_array<IBase>& a)
{
    BOOST_CHECK(a.size() == 5);
    for (int i = 0; i < 5; ++i) {
        BOOST_CHECK_EQUAL(a[i]->getN(), i);
    }
}

void do_in_2(IBase** a, uint32_t s, uint32_t l)
{
    do_in_impl_2(impl::ptr_array_in<IBase>(a, s, l));
}

void do_in2_2(IBase** a, uint32_t s)
{
    do_in_impl_2(impl::ptr_array_in2<IBase>(a, s));
}

void do_out_impl_1(ptr_array<IBase>& a)
{
    BOOST_CHECK(a.empty());
}

void do_out_1(IBase*** a, uint32_t* s, uint32_t* l)
{
    do_out_impl_1(impl::ptr_array_out<IBase>(a, s, l));
}

void do_out2_1(IBase*** a, uint32_t* s)
{
    do_out_impl_1(impl::ptr_array_out2<IBase>(a, s));
}

void do_out_impl_2(ptr_array<IBase>& a)
{
    BOOST_CHECK(a.empty());
    a.reserve(10);
    a.resize(6);
    for (int i = 0; i < 6; ++i) {
        a[i].reset(X_make_instance());
        a[i]->setN(i);
    }
}

void do_out_2(IBase*** a, uint32_t* s, uint32_t* l)
{
    do_out_impl_2(impl::ptr_array_out<IBase>(a, s, l));
}

void do_out2_2(IBase*** a, uint32_t* s)
{
    do_out_impl_2(impl::ptr_array_out2<IBase>(a, s));
}

void do_out_impl_3(ptr_array<IBase>& a)
{
    BOOST_CHECK(a.empty());
    a.reserve(10);
}

void do_out_3(IBase*** a, uint32_t* s, uint32_t* l)
{
    do_out_impl_3(impl::ptr_array_out<IBase>(a, s, l));
}

void do_out2_3(IBase*** a, uint32_t* s)
{
    do_out_impl_3(impl::ptr_array_out2<IBase>(a, s));
}

ptr_array<IBase> do_out_impl_retval()
{
    ptr_array<IBase> ret(10);
    ret.reserve(20);
    for (int i = 0; i < 10; i++) {
        ret[i].reset(X_make_instance());
        ret[i]->setN(i);
    }
    return ret;
}

void do_out_retval(IBase*** a, uint32_t* s, uint32_t* l)
{
    impl::ptr_array_out<IBase> retval(a, s, l);
    retval = do_out_impl_retval();
}

void do_out2_retval(IBase*** a, uint32_t* s)
{
    impl::ptr_array_out2<IBase> retval(a, s);
    retval = do_out_impl_retval();
}

void do_inout_impl_1(ptr_array<IBase>& a)
{
    BOOST_CHECK(a.empty());
    a.reserve(10);
    a.resize(4);
    for (int i = 0; i < 4; ++i) {
        a[i].reset(X_make_instance());
        a[i]->setN(i * 10);
    }
}

void do_inout_1(IBase*** a, uint32_t* s, uint32_t* l)
{
    do_inout_impl_1(impl::ptr_array_inout<IBase>(a, s, l));
}

void do_inout2_1(IBase*** a, uint32_t* s)
{
    do_inout_impl_1(impl::ptr_array_inout2<IBase>(a, s));
}

void do_inout_impl_2(ptr_array<IBase>& a)
{
    BOOST_CHECK(a.size() == 5);
    for (int i = 0; i < 5; ++i) {
        BOOST_CHECK_EQUAL(a[i]->getN(), i * 10);
    }

    std::reverse(a.begin(), a.end());

    a.resize(4);
}

void do_inout_2(IBase*** a, uint32_t* s, uint32_t* l)
{
    do_inout_impl_2(impl::ptr_array_inout<IBase>(a, s, l));
}

void do_inout2_2(IBase*** a, uint32_t* s)
{
    do_inout_impl_2(impl::ptr_array_inout2<IBase>(a, s));
}

void do_inout_impl_3(ptr_array<IBase>& a)
{
    BOOST_CHECK(a.size() == 5);
    for (int i = 0; i < 5; ++i) {
        BOOST_CHECK_EQUAL(a[i]->getN(), i * 10);
    }

    a.clear();
    a.reserve(20);
    a.resize(18);
    for (int i = 0; i < 18; ++i) {
        a[i].reset(X_make_instance());
        a[i]->setN(i * 100);
    }
}

void do_inout_3(IBase*** a, uint32_t* s, uint32_t* l)
{
    do_inout_impl_3(impl::ptr_array_inout<IBase>(a, s, l));
}

void do_inout2_3(IBase*** a, uint32_t* s)
{
    do_inout_impl_3(impl::ptr_array_inout2<IBase>(a, s));
}

void do_inout_impl_4(ptr_array<IBase>& a)
{
    BOOST_CHECK(a.size() == 10);
    for (int i = 0; i < 10; ++i) {
        BOOST_CHECK_EQUAL(a[i]->getN(), i * 100);
    }

    a.clear();
}

void do_inout_4(IBase*** a, uint32_t* s, uint32_t* l)
{
    do_inout_impl_4(impl::ptr_array_inout<IBase>(a, s, l));
}

void do_inout2_4(IBase*** a, uint32_t* s)
{
    do_inout_impl_4(impl::ptr_array_inout2<IBase>(a, s));
}

void do_out_isize2_impl_1(ptr_array<IBase>& a, uint32_t n)
{
    BOOST_CHECK(a.empty());
    a.resize(n);
    ref_ptr<IBase> p(X_make_instance());
    p->setN(8);
    for (int i = 0; i < (int)n; ++i) {
        a[i] = p;
    }
}

void do_out_isize2_1(IBase*** a, uint32_t n)
{
    do_out_isize2_impl_1(impl::ptr_array_out_isize2<IBase>(a, n), n);
}

void do_out_isize2_impl_2(ptr_array<IBase>& a, uint32_t n)
{
    BOOST_CHECK(a.empty());
    a.resize(n - 1); // to produce less data, cause error
}

void do_out_isize2_2(IBase*** a, uint32_t n)
{
    do_out_isize2_impl_2(impl::ptr_array_out_isize2<IBase>(a, n), n);
}

ptr_array<IBase> do_out_isize2_impl_retval(uint32_t n)
{
    ptr_array<IBase> a(n);
    ref_ptr<IBase> p(X_make_instance());
    p->setN(8);
    for (int i = 0; i < (int)n; ++i) {
        a[i] = p;
    }
    return a;
}

void do_out_isize2_retval(IBase*** a, uint32_t n)
{
    impl::ptr_array_out_isize2<IBase> retval(a, n);
    retval = do_out_isize2_impl_retval(n);
}

}
}

BOOST_AUTO_TEST_CASE(test_impl_ptr_array_in)
{
    using namespace xpcom;

    X_count_guard g;
    X_count(0);
    {
        IBase** a1 = 0;
        IBase** a2 = memory::alloc<IBase*>(10);
        for (int i = 0; i < 5; ++i) {
            ref_ptr<IBase> p(X_make_instance());
            p->setN(i);
            a2[i] = p.release();
        }

        do_in_1(a1, 0, 0);
        do_in_2(a2, 10, 5);

        for (int i = 0; i < 5; ++i) {
            a2[i]->release();
        }
        memory::free(a2);
    }
    BOOST_CHECK_EQUAL(X_count(), 0);
}

BOOST_AUTO_TEST_CASE(test_impl_ptr_array_in2)
{
    using namespace xpcom;

    X_count_guard g;
    X_count(0);
    {
        IBase** a1 = 0;
        IBase** a2 = memory::alloc<IBase*>(10);
        for (int i = 0; i < 5; ++i) {
            ref_ptr<IBase> p(X_make_instance());
            p->setN(i);
            a2[i] = p.release();
        }

        do_in2_1(a1, 0);
        do_in2_2(a2, 5);

        for (int i = 0; i < 5; ++i) {
            a2[i]->release();
        }
        memory::free(a2);
    }
    BOOST_CHECK_EQUAL(X_count(), 0);
}

BOOST_AUTO_TEST_CASE(test_impl_ptr_array_out)
{
    using namespace xpcom;

    X_count_guard g;
    X_count(0);
    {
        IBase** a = 0;
        uint32_t s;
        uint32_t l;

        do_out_1(&a, &s, &l);
        BOOST_CHECK(a == 0);
        BOOST_CHECK(s == 0);
        BOOST_CHECK(l == 0);

        do_out_2(&a, &s, &l);
        BOOST_CHECK(l == 6);
#ifdef XPCOM_ENABLE_LENGTH_IS
        BOOST_CHECK(s == 10);
#endif
        for (int i = 0; i < 6; ++i) {
            BOOST_CHECK_EQUAL(a[i]->getN(), i);
            a[i]->release();
        }
        memory::free(a);

        do_out_3(&a, &s, &l);
        BOOST_CHECK(l == 0);
#ifdef XPCOM_ENABLE_LENGTH_IS
        BOOST_CHECK(s == 10);
#endif
        memory::free(a);

        do_out_retval(&a, &s, &l);
        BOOST_CHECK(l = 10);
        for (int i = 0; i < 10; ++i) {
            BOOST_CHECK_EQUAL(a[i]->getN(), i);
            a[i]->release();
        }
        memory::free(a);
    }
    BOOST_CHECK_EQUAL(X_count(), 0);
}

BOOST_AUTO_TEST_CASE(test_impl_ptr_array_out2)
{
    using namespace xpcom;

    X_count_guard g;
    X_count(0);
    {
        IBase** a = 0;
        uint32_t s;

        do_out2_1(&a, &s);
        BOOST_CHECK(a == 0);
        BOOST_CHECK(s == 0);

        do_out2_2(&a, &s);
        BOOST_CHECK(s == 6);
        for (int i = 0; i < 6; ++i) {
            BOOST_CHECK_EQUAL(a[i]->getN(), i);
            a[i]->release();
        }
        memory::free(a);

        do_out2_3(&a, &s);
        BOOST_CHECK(s == 0);
        memory::free(a);

        do_out2_retval(&a, &s);
        BOOST_CHECK(s = 10);
        for (int i = 0; i < 10; ++i) {
            BOOST_CHECK_EQUAL(a[i]->getN(), i);
            a[i]->release();
        }
        memory::free(a);
    }
    BOOST_CHECK_EQUAL(X_count(), 0);
}

BOOST_AUTO_TEST_CASE(test_impl_ptr_array_inout)
{
    using namespace xpcom;

    X_count_guard g;
    X_count(0);
    {
        IBase** a = 0;
        uint32_t s = 0;
        uint32_t l = 0;

        do_inout_1(&a, &s, &l);
        BOOST_CHECK(l == 4);
#ifdef XPCOM_ENABLE_LENGTH_IS
        BOOST_CHECK(s == 10);
#endif
        for (int i = 0; i < 4; ++i) {
            BOOST_CHECK_EQUAL(a[i]->getN(), i * 10);
        }

        a = memory::realloc(a, 20);
        s = 20;
        l = 5;
        a[4] = X_make_instance();
        a[4]->add_ref();
        a[4]->setN(40);
        do_inout_2(&a, &s, &l);
        BOOST_CHECK(l == 4);
        BOOST_CHECK_EQUAL(a[0]->getN(), 40); a[0]->release();
        BOOST_CHECK_EQUAL(a[1]->getN(), 30); a[1]->release();
        BOOST_CHECK_EQUAL(a[2]->getN(), 20); a[2]->release();
        BOOST_CHECK_EQUAL(a[3]->getN(), 10); a[3]->release();

        a = memory::realloc(a, 8);
        s = 8;
        l = 5;
        for (int i = 0; i < 5; ++i) {
            a[i] = X_make_instance();
            a[i]->add_ref();
            a[i]->setN(i * 10);
        }
        do_inout_3(&a, &s, &l);
        BOOST_CHECK(l == 18);
#ifdef XPCOM_ENABLE_LENGTH_IS
        BOOST_CHECK(s == 20);
#endif
        for (int i = 0; i < 18; ++i) {
            BOOST_CHECK_EQUAL(a[i]->getN(), i * 100);
        }

        for (int i = 10; i < 18; ++i) {
            a[i]->release();
        }
        a = memory::realloc(a, 12);
        s = 12;
        l = 10;
        do_inout_4(&a, &s, &l);
        BOOST_CHECK(l == 0);
        memory::free(a);
    }
    BOOST_CHECK_EQUAL(X_count(), 0);
}

BOOST_AUTO_TEST_CASE(test_impl_ptr_array_inout2)
{
    using namespace xpcom;

    X_count_guard g;
    X_count(0);
    {
        IBase** a = 0;
        uint32_t s = 0;

        do_inout2_1(&a, &s);
        BOOST_CHECK(s == 4);
        for (int i = 0; i < 4; ++i) {
            BOOST_CHECK_EQUAL(a[i]->getN(), i * 10);
        }

        a = memory::realloc(a, 5);
        s = 5;
        a[4] = X_make_instance();
        a[4]->add_ref();
        a[4]->setN(40);
        do_inout2_2(&a, &s);
        BOOST_CHECK(s == 4);
        BOOST_CHECK_EQUAL(a[0]->getN(), 40); a[0]->release();
        BOOST_CHECK_EQUAL(a[1]->getN(), 30); a[1]->release();
        BOOST_CHECK_EQUAL(a[2]->getN(), 20); a[2]->release();
        BOOST_CHECK_EQUAL(a[3]->getN(), 10); a[3]->release();

        a = memory::realloc(a, 15);
        s = 5;
        for (int i = 0; i < 5; ++i) {
            a[i] = X_make_instance();
            a[i]->add_ref();
            a[i]->setN(i * 10);
        }
        do_inout2_3(&a, &s);
        BOOST_CHECK(s == 18);
        for (int i = 0; i < 18; ++i) {
            BOOST_CHECK_EQUAL(a[i]->getN(), i * 100);
        }

        for (int i = 10; i < 18; ++i) {
            a[i]->release();
        }
        a = memory::realloc(a, 12);
        s = 10;
        do_inout2_4(&a, &s);
        BOOST_CHECK(s == 0);
        memory::free(a);
    }
    BOOST_CHECK_EQUAL(X_count(), 0);
}

BOOST_AUTO_TEST_CASE(test_impl_ptr_array_out_isize2)
{
    using namespace xpcom;

    X_count_guard g;
    X_count(0);
    {
        IBase** a = 0;

        do_out_isize2_1(&a, 8);
        for (int i = 0; i < 8; ++i) {
            BOOST_CHECK_EQUAL(a[i]->getN(), 8);
            a[i]->release();
        }
        memory::free(a);

        do_out_isize2_1(&a, 0);
        BOOST_CHECK(a == 0);

#ifdef NDEBUG // to avoid assertion failure
        do_out_isize2_2(&a, 5); // returned length less than requested
        BOOST_CHECK(a == 0);
#endif

        do_out_isize2_retval(&a, 10);
        for (int i = 0; i < 10; ++i) {
            BOOST_CHECK_EQUAL(a[i]->getN(), 8);
            a[i]->release();
        }
        memory::free(a);
    }
    BOOST_CHECK_EQUAL(X_count(), 0);
}

